package com.hainingtong.core.config;


import com.hainingtong.core.filter.JwtFilter;
import com.hainingtong.core.shiro.MyModularRealmAuthenticator;
import com.hainingtong.core.shiro.realm.JwtRealm;
import com.hainingtong.core.shiro.realm.PhoneCodeRealm;
import com.hainingtong.core.shiro.realm.UsernamePasswordRealm;
import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;

import javax.servlet.Filter;
import java.util.*;

@Configuration
public class ShiroConfig {

    @Autowired
    private ConfigurableEnvironment env;

    @Bean
    public Authorizer authorizer(){
        return new ModularRealmAuthorizer();
    }

    //将自己的验证方式加入容器，如果有多个可以在下面配置，只要类型不同即可
    @Bean
    public UsernamePasswordRealm usernamePasswordRealm() {
        return new UsernamePasswordRealm();
    }

    //将自己的验证方式加入容器
    @Bean
    public PhoneCodeRealm phoneCodeRealm() {
        return new PhoneCodeRealm();
    }

    @Bean
    public JwtRealm jwtRealm(){
        JwtRealm realm = new JwtRealm();

        realm.setCachingEnabled(false);
        return realm;
    }

    /**
     * 因为使用jwt登录，无需session，所以要取消session，减少不必要的开销，【但是会导致druid页面无法登录】
     * 参考文章：https://developpaper.com/spring-boot-plus-integrates-spring-boot-shiro-jwt-permission-management/
     * @return
     */
    @Bean
    public DefaultSessionManager sessionManager() {
        DefaultSessionManager manager = new DefaultSessionManager();
        manager.setSessionValidationSchedulerEnabled(false);
        return manager;
    }

    @Bean
    public SessionStorageEvaluator sessionStorageEvaluator() {
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    @Bean
    public DefaultSubjectDAO subjectDAO() {
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        return defaultSubjectDAO;
    }

    /**
     * 初始化Authenticator验证管理器，如不注入，则会导致验证失败返回未登录
     * Authorizer授权器：赋予主体有哪些权限
     */
    @Bean
    public Authenticator authenticator() {
        //扩展父类原方法，捕获原始异常
        ModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator();

        /**
         FirstSuccessfulStrategy：只要有一个 Realm 验证成功即可，只返回第一个 Realm 身份验证成功的认证信息，其他的忽略；
         AtLeastOneSuccessfulStrategy：只要有一个 Realm 验证成功即可，和 FirstSuccessfulStrategy 不同，返回所有 Realm 身份验证成功的认证信息；（默认）
         AllSuccessfulStrategy：所有 Realm 验证成功才算成功，且返回所有 Realm 身份验证成功的认证信息，如果有一个失败就失败了。
         */
        //设置多个realm认证策略，一个成功即跳过其它的
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return authenticator;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(authenticator());
        securityManager.setRealms(Arrays.asList(usernamePasswordRealm(),phoneCodeRealm(),jwtRealm()));
        // 如果是线上模式，就关闭session
        // 主要原因是：druid监控页面登录需要session，所以开发模式下开启session，线上模式不需要druid就关闭了
        // 通过配置 spring.profiles.active 属性来判断是否是开发模式，默认值是空字符串
        String v = env.getProperty("spring.profiles.active");
        if("prod".equals(v)) {
            securityManager.setSubjectDAO(subjectDAO());
            securityManager.setSessionManager(sessionManager());
        }
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }


    /**
     *Shirofilterfactorybean configuration
     *
     * @param securityManager
     * @param jwtFilter
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,JwtFilter jwtFilter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/api/v1/user/login");
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", jwtFilter);
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
        return shiroFilterFactoryBean;
    }


    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

        chainDefinition.addPathDefinition("/api/v1/user/login/**", "anon");
        chainDefinition.addPathDefinition("/api/v1/user/register/**", "anon");

//        // logged in users with the 'admin' role
//        chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");
//
//        // logged in users with the 'document:read' permission
//        chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]");

        // all other paths require a logged in user
        chainDefinition.addPathDefinition("/api/v1/**", "jwt");
        chainDefinition.addPathDefinition("/**", "anon");

        return chainDefinition;
    }
}